/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace NTasking.Internals
{

    /// <summary>
    /// Represent the base of all synchronized methods and method families
    /// </summary>
    public abstract class SynchronizedMethod
    {

        #region Fields

        /// <summary>
        /// Stores the list of conditions in which this instance can be accepted
        /// </summary>
        internal readonly List<ExecutionContext> AcceptableCalls;

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether this instance is already beeing accepted.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance is already beeing accepted; otherwise, <c>false</c>.
        /// </value>
        internal bool CurrentlyBeingAccepted { get; set; }


        /// <summary>
        /// Gets or sets the name of this instance.
        /// </summary>
        /// <value>The name of this instance.</value>
        internal string Name { get; set; }


        /// <summary>
        /// Gets a value indicating whether this instance has a body.
        /// </summary>
        /// <value><c>true</c> if this instance has a body; otherwise, <c>false</c>.</value>
        internal abstract bool HasBody { get; }


        /// <summary>
        /// Gets or Sets a value indicating whether this instance is an Entry
        /// </summary>
        /// <value><c>true</c> if this instance is an Entry; otherwise <c>false</c>.</value>
        internal bool IsEntry { get; set; }


        /// <summary>
        /// Gets or Sets a value indicating whether this instance is a read-only method
        /// </summary>
        /// <value><c>true</c> if this instance this instance is a read-only method; otherwise <c>false</c>.</value>
        internal bool IsReadOnly { get; set; }


        /// <summary>
        /// Gets or sets the <see cref="SynchronizedObject"/> of which this instance is a synchronized method.
        /// </summary>
        /// <value>The owner.</value>
        internal SynchronizedObject Owner { get; set; }


        /// <summary>
        /// Gets the number of this instance's parameters
        /// </summary>
        /// <value>The number of this instance's parameters.</value>
        internal int ParametersCount
        {
            get { return ParametersProfile.Length; }
        }


        /// <summary>
        /// Gets the types of this instance's parameters.
        /// </summary>
        /// <value>The types of this instance's parameters.</value>
        internal abstract Type[] ParametersProfile { get; }


        /// <summary>
        /// Gets the type of this instance's result.
        /// </summary>
        /// <value>The type of this instance's result.</value>
        internal abstract Type ResultType { get; }


        /// <summary>
        /// Gets or sets the <see cref="SynchronizedMethod"/> of which this instance is a member.
        /// </summary>
        /// <value>The <see cref="SynchronizedMethod"/> of which this instance is a member.</value>
        internal SynchronizedMethod Family { get; set; }

        #endregion


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SynchronizedMethod"/> class.
        /// </summary>
        internal SynchronizedMethod()
        {
            Family = this;
            AcceptableCalls = new List<ExecutionContext>();
        }

        #endregion


        #region Static Methods

        /// <summary>
        /// Determines whether parameter types are compatible for requeue when using strict Ada compatibility
        /// </summary>
        /// <param name="requeue">The requeue.</param>
        /// <param name="from">From.</param>
        /// <returns>
        /// 	<c>true</c> if types are requeueable; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsStrictlyRequeueable(IList<Type> requeue, IList<Type> from)
        {
            if (requeue.Count == 0)
                return true;

            if (requeue.Count != from.Count)
                return false;

            for (int i = 0; i < requeue.Count; i++)
                if (requeue[i] != from[i])
                    return false;

            return true;
        }


        /// <summary>
        /// Determines whether parameter types are compatible for requeue when using loose Ada compatibility
        /// </summary>
        /// <param name="requeue">The requeue.</param>
        /// <param name="from">From.</param>
        /// <returns>
        /// 	<c>true</c> if types are requeueable; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsLooselyRequeueable(IList<Type> requeue, IList<Type> from)
        {
            if (from.Count < requeue.Count)
                return false;

            for (int i = 0; i < requeue.Count; i++)
            {
                if (from[i].IsValueType && !requeue[i].IsValueType)
                    return false;
                if (!requeue[i].IsAssignableFrom(from[i]))
                    return false;
            }

            return true;
        }


        /// <summary>
        /// Determines whether result types are compatible for requeue when using loose Ada compatibility
        /// </summary>
        /// <param name="requeue">The requeue.</param>
        /// <param name="from">From.</param>
        /// <returns>
        /// 	<c>true</c> if types are requeueable; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsLooselyRequeueable(Type requeue, Type from)
        {
            if (requeue.IsValueType && !from.IsValueType)
                return false;

            return from.IsAssignableFrom(requeue);
        }

        #endregion


        #region Methods

        /// <summary>
        /// Get a string which represent the parameter types.
        /// </summary>
        /// <returns></returns>
        private string ParametersProfileString()
        {
            var param = ParametersProfile;
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < param.Length; i++)
            {
                if (i > 0)
                    sb.Append(",");
                sb.Append(param[i]);
            }

            return sb.ToString();
        }


        /// <summary>
        /// Determines whether the specified <see cref="SynchronizedMethod"/> can be requeued to this instance.
        /// </summary>
        /// <param name="from">The <see cref="SynchronizedMethod"/> to be checked.</param>
        /// <returns>
        ///     <c>true</c> if the specified <see cref="SynchronizedMethod"/> can be requeued to this instance; otherwise, <c>false</c>.
        /// </returns>
        internal bool IsRequeueable(SynchronizedMethod from)
        {
            if (Settings.RequeueMode == AdaCompatibilityMode.StrictCompatibility)
                return
                    IsStrictlyRequeueable(ParametersProfile, from.ParametersProfile) &&
                    from.ResultType == ResultType;

            return IsLooselyRequeueable(ParametersProfile, from.ParametersProfile) &&
                   (from.ResultType == typeof(Void) || IsLooselyRequeueable(ResultType, from.ResultType));
        }


        /// <summary>
        /// Retrieve a <see cref="T:System.String"/> which represents this instance, including Owner and Index if any.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> which represents this instance.</returns>
        internal string FullName()
        {
            return string.Format("{0}.{1}{2}", Owner, Name, IndexRepresentation());
        }


        /// <summary>
        /// Retrieve a <see cref="T:System.String"/> which represents this instance's Index if any.
        /// </summary>
        /// <returns>A <see cref="T:System.String"/> which represents this instance's Index if any; otherwise an empty string.</returns>
        internal virtual string IndexRepresentation()
        {
            return "";
        }


        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0}({1})", FullName(), ParametersProfileString());
        }


        /// <summary>
        /// Creates a <see cref="MethodCall"/> which represents a requeue onto the current instance.
        /// </summary>
        /// <returns>A <see cref="MethodCall"/> which represents a requeue onto the current instance</returns>
        internal abstract MethodCall CreateRequeueCall();

        /// <summary>
        /// Executes the specified call in the specified executon context.
        /// </summary>
        /// <param name="call">The call.</param>
        /// <param name="context">The context.</param>
        internal abstract void Execute(MethodCall call, ExecutionContext context);

        #endregion

    }


    /// <summary>
    /// Represent the synchronized methods and method families which can be called with a given parameter profile,
    /// selectvely called wth a given parameter profile,  executed with a given parameter profile and which return
    /// a specific type
    /// </summary>
    /// <typeparam name="RetT">The type of the synchronized method's result.</typeparam>
    /// <typeparam name="CallDelegateT">A delegate type representing the calling profile for performing a synchronous call.</typeparam>
    /// <typeparam name="IDelegateT">A delegate type representing the synchronized method calling profile for intercepting a call.</typeparam>
    /// <typeparam name="ExecuteDelegateT">A delegate type representing the calling profile for executing a call</typeparam>
    public abstract class SynchronizedMethod<RetT, CallDelegateT, IDelegateT, ExecuteDelegateT> :
        SynchronizedMethod,
        ISynchronouslyCallable<CallDelegateT>,
        ICallable<IDelegateT>,
        IExecutable<RetT, ExecuteDelegateT>

        where CallDelegateT : class // Should be MulticastDelegate but refused by the compiler
        where IDelegateT : class // Should be MulticastDelegate but refused by the compiler
        where ExecuteDelegateT : class // Should be MulticastDelegate but refused by the compiler
    {

        #region Static Fields

        private static readonly Type _resultType = typeof(RetT);
        private static readonly Type[] _parametersProfile;

        #endregion


        #region Static Constructor

        /// <summary>
        /// Initializes the <see cref="SynchronizedMethod{RetT, CallDelegateT, IDelegateT,ExecuteDelegateT}"/> class.
        /// </summary>
        static SynchronizedMethod()
        {
            var types = typeof(CallDelegateT).GetGenericArguments();
            var len = types.Length;

            if (typeof(RetT) != typeof(Void))
                len = len - 1;

            _parametersProfile = new Type[len];
            for (int i = 0; i < len; i++)
                _parametersProfile[i] = types[i];
        }

        #endregion


        #region Properties

        /// <summary>
        /// Gets a value indicating whether this instance has a body.
        /// </summary>
        /// <value>
        ///     <c>true</c> if this instance has a body; otherwise, <c>false</c>.
        /// </value>
        internal override bool HasBody { get { return AcceptableCalls.Count > 0 || (this as IExecutable<ExecuteDelegateT>).ProtectedBody != null; } }


        /// <summary>
        /// Gets the type of this instance's result.
        /// </summary>
        /// <value>The type of this instance's result.</value>
        internal override Type ResultType
        {
            get { return _resultType; }
        }


        /// <summary>
        /// Gets the types of this instance's parameters.
        /// </summary>
        /// <value>The types of this instance's parameters.</value>
        internal override Type[] ParametersProfile
        {
            get { return _parametersProfile; }
        }


        /// <summary>
        /// Gets or sets the <see cref="SynchronizedMethod"/> of which this instance is a member.
        /// </summary>
        /// <value>
        /// The <see cref="SynchronizedMethod"/> of which this instance is a member.
        /// </value>
        SynchronizedMethod ISynchronizable.Family
        {
            get { return Family; }
        }


        /// <summary>
        /// Gets or sets a delegate which can be used to create an <see cref="ExecutionContext"/> representing
        /// the execution of the current instance.
        /// </summary>
        /// <value>
        /// A delegate which can be used to create an <see cref="ExecutionContext"/>.
        /// </value>
        Func<ExecutionContext> IExecutable.CreateContext
        {
            get { return CreateExecutionContext; }
        }


        ExecuteDelegateT IExecutable<ExecuteDelegateT>.ProtectedBody
        {
            get;
            set;
        }


        RetT IExecutable<RetT, ExecuteDelegateT>.Execute(CallContext context, ExecuteDelegateT body)
        {
            return Execute(context, body);
        }

        #endregion


        #region Methods

        /// <summary>
        /// Executes the specified call.
        /// </summary>
        /// <param name="call">The call.</param>
        /// <param name="exec">The exec.</param>
        internal override void Execute(MethodCall call, ExecutionContext exec)
        {
            var execute = (ExecutionContext<ExecuteDelegateT>)exec;

            try
            {
                if (execute.HasBody)
                    call.Context.SetResult((this as IExecutable<RetT, ExecuteDelegateT>).Execute(call.Context, execute.Body));
                else
                    call.Context.SetResult();
                execute.SetResult();
            }
            catch (ThreadAbortException ex)
            {
                if (!ThreadWrapper.IsTaskRequeue(ex))
                {
                    // If the task processing a rendez-vous is aborted,
                    // then TaskingError must be raised in the caller 
                    call.Context.Exception = Errors.AcceptAborted(call);
                }

                // Normally a ThreadAbortException is automatically rethrown unless we do a Thread.ResetAbort
                // but in some cases this does not seem to work so force rethrowing
                throw;
            }
            catch (Exception ex)
            {
                // Any Exception thrown by the execution of the call will propagate to both the caller
                // and acceptor

                // On the caller side, the result is set to the exception thrown during processing
                call.Context.Exception = ex;
                ex.PreserveStackTrace(true);

                // On the acceptor side, the result is set to a RendezVousError in order to avoid
                // too much non-essential information to be added to the exception's stack trace
                execute.Exception = new RendezVousError(ex.DeepCopy());
                execute.Exception.InnerException.PreserveStackTrace(false);
            }
        }


        /// <summary>
        /// Retrieve a delegate which can be used to effectively perform a direct call
        /// of the current instance
        /// </summary>
        /// <returns>
        /// A delegate which can be used to effectively perform a direct call of the current instance
        /// </returns>
        CallDelegateT ISynchronouslyCallable<CallDelegateT>.SynchronousCall()
        {
            return SynchronousCall();
        }


        /// <summary>
        /// Retrieve a delegate which can be used to capture the call of the current
        /// instance into a <see cref="MethodCall"/>
        /// </summary>
        /// <returns>
        /// A delegate which can be used to capture a call of the current instance
        /// </returns>
        IDelegateT ICallable<IDelegateT>.Intercept
        {
            get { return Intercept; }
        }


        /// <summary>
        /// Retrieve a delegate which can be used to capture the call of the current
        /// instance into a <see cref="MethodCall"/>
        /// </summary>
        /// <returns>
        /// A delegate which can be used to capture a call of the current instance
        /// </returns>
        internal abstract IDelegateT Intercept { get; }


        /// <summary>
        /// Create an <see cref="ExecutionContext"/> representing an accept of the current instance.
        /// </summary>
        /// <value>An <see cref="ExecutionContext"/> representing an accept of the current instance.</value>
        internal abstract ExecutionContext CreateExecutionContext();

        /// <summary>
        /// Get a delegate which can be used to perform an synchronous call of this instance.
        /// </summary>
        /// <returns>
        /// A delegate which can be used to perform a synchronous call on this object
        /// </returns>
        protected abstract CallDelegateT SynchronousCall();


        /// <summary>
        /// Execute the specified call using the specified method body and return the produced result.
        /// </summary>
        /// <param name="context">The parameters of the call.</param>
        /// <param name="body">The method body to be executed</param>
        /// <returns>The result of executing the call</returns>
        internal abstract RetT Execute(CallContext context, ExecuteDelegateT body);

        #endregion
    }

}
